package org.budo.weixin.api;

import java.io.InputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Map;

import org.budo.support.http.client.HttpUtil;
import org.budo.support.http.client.Request;
import org.budo.support.http.client.Response;
import org.budo.support.lang.util.MapUtil;
import org.budo.support.lang.util.NumberUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.weixin.api.response.SendMessageResponse;
import org.budo.weixin.api.response.SnsTokenResponse;
import org.budo.weixin.api.response.SnsUserInfoResponse;
import org.budo.weixin.api.response.UserInfoResponse;
import org.budo.weixin.message.Message;
import org.budo.weixin.message.TemplateMessage;
import org.budo.weixin.message.TextMessage;
import org.budo.weixin.util.WeixinUtil;
import org.springframework.util.Assert;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.qq.weixin.mp.aes.AesException;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;

/**
 * @author lmw
 */
@Getter
@Setter
@Slf4j
@Accessors(chain = true)
public class WeixinApiImpl implements WeixinApi, Serializable {
    private static final long serialVersionUID = 7166848480112250459L;

    private static final String LANG_ZH_CN = "zh_CN";

    private String accessToken;

    private String componentAppId;

    /**
     * 获取授权方式微信access_token和refresh_token，如果单独调用，则需要将authorizer_access_token 和
     * authorizer_refresh_token写入渠道表 { "authorizer_access_token":
     * "aaUl5s6kAByLwgV0BhXNuIFFUqfrR8vTATsoSHukcIGqJgrc4KmMJ-JlKoC_-NKCLBvuU1cWPv4vDcLN8Z0pn5I45mpATruU0b51hzeT1f8",
     * "expires_in": 7200, "authorizer_refresh_token":
     * "BstnRqgTJBXb9N2aJq6L5hzfJwP406tpfahQeLNxX0w" }
     */
    public Map<String, Object> getAuthorizerWeixinToken(String appId, String authorizerRefreshToken, String componentAppId, String componentAccessToken) {
        String url = "https://api.weixin.qq.com/cgi-bin/component/api_authorizer_token?component_access_token=" + componentAccessToken;

        Request request = new Request();
        request.setUrl(url);

        Map<String, Object> data = MapUtil.stringObjectMap("component_appid", componentAppId, "authorizer_appid", appId, "authorizer_refresh_token", authorizerRefreshToken);
        request.setMethod(Request.POST);
        request.setRequestBody(JSON.toJSONBytes(data));

        Response response = request.execute();
        log.info("#60 getAuthorizerWeixinToken, response=" + (null == response ? response : response.toSimpleString()));

        String result = response.getBody();
        Map<String, Object> map = JSON.parseObject(result);
        String token = StringUtil.toString(map.get("authorizer_access_token"));

        if (StringUtil.isEmpty(token)) {
            log.error("#66 获取微信Token失败, appId={},authorizerRefreshToken={}，componentAppId={}, componentAccessToken={}, response={}", //
                    appId, authorizerRefreshToken, componentAppId, componentAccessToken, response);
        }

        return map;
    }

    /**
     * 获取微信二维码
     */
    public String getWeixinQRCode() {
        String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + this.getAccessToken();

        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.POST);
        request.setRequestBody("{\"action_name\": \"QR_LIMIT_SCENE\", \"action_info\": {\"scene\": {\"scene_id\": 123}}}".getBytes());
        Response response = request.execute();
        log.info("#79 getWeixinQRCode, response=" + (null == response ? response : response.toSimpleString()));

        String result = response.getBody();
        Map<String, Object> map = JSON.parseObject(result);
        String ticket = StringUtil.toString(map.get("ticket"));

        if (StringUtil.isEmpty(ticket)) {
            log.error("#86 getWeixinQRCode error, token={}, response={}", getAccessToken(), result);
            return null;
        }

        return "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + ticket;
    }

    /**
     * @throws AesException
     * @see org.budo.weixin.work.api.WorkWeixinApi#sendMessage(org.budo.weixin.work.WorkWeixinMessage)
     */
    @Override
    public SendMessageResponse sendMessage(Message message) {
        if (null == message) {
            log.error("#99 message is null, message=" + message);
            return null;
        }

        if (message instanceof TextMessage && StringUtil.isEmpty(((TextMessage) message).getContent())) {
            log.error("#104 message.content is null, message=" + message);
            return new SendMessageResponse() //
                    .setResponseBody("message.content is null");
        }

        String url = null;
        if (message instanceof TemplateMessage) {
            url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + this.getAccessToken();
        } else {
            url = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + this.getAccessToken();
        }

        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.POST);
        String messageToJson = WeixinUtil.messageToJson(message);

        request.setRequestBody(messageToJson.getBytes());
        Response response = request.execute();

        log.info("#125 after WeixinApi sendMessage To:{} Response：{}", url, response);
        String responseBody = response.getBody();
        return new SendMessageResponse().setResponseBody(responseBody);
    }

    public String createMenu(String menusToString) {
        Request request = new Request();
        request.setUrl("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + getAccessToken());
        request.setMethod(Request.POST);
        request.setRequestBody(menusToString.getBytes());
        Response response = request.execute();

        log.info("#136 createMenu, response=" + (null == response ? response : response.toSimpleString()));
        return response.getBody();
    }

    public String getMenu() {
        Request request = new Request();
        request.setUrl("https://api.weixin.qq.com/cgi-bin/menu/get?access_token=" + getAccessToken());
        request.setMethod(Request.GET);
        Response response = request.execute();

        log.info("#146 getMenu, response=" + (null == response ? response : response.toSimpleString()));
        return response.getBody();
    }

    public String deleteMenu() {
        Request request = new Request();
        request.setUrl("https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=" + getAccessToken());
        request.setMethod(Request.GET);
        Response response = request.execute();

        log.info("#157 deleteMenu, response=" + (null == response ? response : response.toSimpleString()));
        return response.getBody();
    }

    /**
     * 上传资源
     * 
     * @param type  image voice video thumb
     * @param media File URL InputStream byte[]
     * @return 返回 {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
     */
    public WeixinMediaUploadResult uploadMedia(String type, Object media) {
        Request request = new Request();
        request.setMethod(Request.POST);
        request.setUrl("http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=" + getAccessToken() + "&type=" + type);
        request.setField("media", media);

        Response response = request.execute();
        WeixinMediaUploadResult weixinMediaUploadResult = new WeixinMediaUploadResult(response.getBody());
        if (!weixinMediaUploadResult.getSuccess()) {
            log.error("#177 uploadMedia failed, response={}, request={}", response, request);
        } else {
            log.info("#179 uploadMedia success, response={}, request={}", response, request);
        }

        return weixinMediaUploadResult;
    }

    /**
     * 微信是跳转
     * 
     * @return 返回的是跳转地址
     */
    public String getMedia(String mediaId) {
        return "https://api.weixin.qq.com/cgi-bin/media/get?access_token=" + this.getAccessToken() + "&media_id=" + mediaId;
    }

    /**
     * 微信是跳转
     * 
     * @return 返回的文件内容
     */
    @Override
    public InputStream downloadMedia(String mediaId) {
        try {
            String mediaUrl = this.getMedia(mediaId);
            return new URL(mediaUrl).openStream();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html {
     * "openid": "oLVPpjqs9BhvzwPj5A-vTYAX3GLc", "nickname": "刺猬宝宝", "sex": 1,
     * "language": "简体中文", "city": "深圳", "province": "广东", "country": "中国",
     * "headimgurl":
     * "http://wx.qlogo.cn/mmopen/utpKYf69VAbCRDRlbUsPsdQN38DoibCkrU6SAMCSNx558eTaLVM8PyM6jlEGzOrH67hyZibIZPXu4BK1XNWzSXB3Cs4qpBBg18/0",
     * "privilege": [] }
     */
    @Override
    public SnsUserInfoResponse getSnsUserInfo(String accessToken, String openId) {
        String url = "https://api.weixin.qq.com/sns/userinfo?access_token=" + accessToken + "&openid=" + openId + "&lang=zh_CN";
        Response response = HttpUtil.get(url);
        String responseBody = response.getBody();

        SnsUserInfoResponse snsUserInfo = JSON.parseObject(responseBody, SnsUserInfoResponse.class);

        if (null == snsUserInfo || StringUtil.isNullOrEmpty(snsUserInfo.getNickname())) {
            log.error("#234 getSnsUserInfo, responseBody=" + responseBody + ", accessToken=" + accessToken + ", openId=" + openId);
            return snsUserInfo;
        }

        log.info("#241 getSnsUserInfo, responseBody=" + responseBody + ", accessToken=" + accessToken + ", openId=" + openId);
        return snsUserInfo;
    }

    /**
     * 获取用户基本信息
     * http://mp.weixin.qq.com/wiki/14/bb5031008f1494a59c6f71fa0f319c66.html
     * 
     * @param openId
     */
    @Override
    public UserInfoResponse getUserInfo(String openId) {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + this.getAccessToken() //
                + "&openid=" + openId //
                + "&lang=" + LANG_ZH_CN;
        Response response = HttpUtil.get(url);
        String responseBody = StringUtil.toString(response);

        // 未关注公众号时返回
        // {"subscribe":0,"openid":"owxg_wrBe3aO5wjTvkVpoDYqflME","tagid_list":[]}
        log.info("#218 getUserInfo, responseBody=" + responseBody);
        return JSON.parseObject(responseBody, UserInfoResponse.class);
    }

    /**
     * http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html#.E7.AC.AC.E4.BA.8C.E6.AD.A5.EF.BC.9A.E9.80.9A.E8.BF.87code.E6.8D.A2.E5.8F.96.E7.BD.91.E9.A1.B5.E6.8E.88.E6.9D.83access_token
     */
    @Override
    public SnsTokenResponse getSnsToken(String appId, String appSecret, String code) {
        String url = "https://api.weixin.qq.com/sns/oauth2/access_token" //
                + "?appid=" + appId //
                + "&secret=" + appSecret //
                + "&code=" + code //
                + "&grant_type=authorization_code";

        Request request = new Request();
        request.setUrl(url);

        request.setMethod(Request.GET);
        Response response = request.execute();

        String responseBody = response.getBody();
        SnsTokenResponse snsTokenResponse = JSON.parseObject(responseBody, SnsTokenResponse.class);

        if (StringUtil.isEmpty(snsTokenResponse.getAccess_token())) {
            log.error("#246 getSnsToken fail, appId={}, appSecret={}, code={}, response={}", appId, appSecret, code, response);
            return snsTokenResponse;
        }

        log.info("#248 get weixin Token success, snsTokenResponse={}", snsTokenResponse);
        return snsTokenResponse;
    }

    public Map<String, Object> getComponentSnsToken(String appId, String code, String componentAppid, String componentAccessToken) {
        String url = "https://api.weixin.qq.com/sns/oauth2/component/access_token" //
                + "?appid=" + appId //
                + "&component_appid=" + componentAppid //
                + "&component_access_token=" + componentAccessToken //
                + "&code=" + code //
                + "&grant_type=authorization_code";

        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.GET);
        Response response = request.execute();
        log.info("#261 getComponentSnsToken, response=" + (null == response ? response : response.toSimpleString()));

        String result = response.getBody();
        Map<String, Object> map = JSON.parseObject(result);
        String token = StringUtil.toString(map.get("access_token"));
        if (StringUtil.isEmpty(token)) {
            log.error("#267 getComponentSnsToken 失败, appId={}, componentAppid={}, code={}, response={}", appId, componentAppid, code, response);
        } else {
            log.info("#269 getComponentSnsTokenToken成功, token={}", token);
        }

        return map;
    }

    @Override
    public String getTicket(String type) {
        Map<String, Object> weixinTicket = this.getWeixinTicket(type);
        return (String) weixinTicket.get("ticket");
    }

    @Override
    public Map<String, Object> getWeixinTicket(String type) {
        String url = "https://api.weixin.qq.com/cgi-bin/ticket/getticket" //
                + "?access_token=" + this.getAccessToken() //
                + "&type=" + type;

        Response response = HttpUtil.get(url);
        String body = response.getBody();
        Map map = JSON.parseObject(body);
        return map;
    }

    public byte[] getSmallAppQrCode(String path, Integer width) throws UnsupportedEncodingException {
        String url = "https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token=" + this.getAccessToken();

        Map fields = MapUtil.stringObjectMap("path", path, "width", width);

        byte[] data = JSON.toJSONString(fields).getBytes("UTF-8");

        Response response = HttpUtil.post(url, null, data);
        log.info("#310 WeixinApi getSmallAppQrCode To:{} Response：{}", url, response);
        return response.getResponseData();
    }

    @Override
    public byte[] getWxaCodeUnlimit(String scene) {
        if (StringUtil.isNullOrEmpty(this.getAccessToken())) {
            log.error("#355 getWxaCodeUnlimit, accessToken is null, " + this);
            return null;
        }

        String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + this.getAccessToken();

        Map<String, Object> parameter = MapUtil.stringObjectMap("scene", scene);
        String json = JSON.toJSONString(parameter);
        byte[] requestBody = StringUtil.getUtf8Bytes(json);

        Response response = HttpUtil.post(url, null, requestBody);
        log.info("#365 WeixinApi getSmallAppLoginQrCode To:{} Response：{}", url, response.getStatus());

        byte[] bytes = response.getResponseData();
        return bytes;
    }

    @Override
    public byte[] getWxaCodeUnlimit(String scene, String page, Integer width, Boolean autoColor, Map<String, Object> lineColor) {
        String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + this.getAccessToken();

        Map<String, Object> parameter = MapUtil.stringObjectMap("scene", scene, //
                "width", width, //
                "page", page, //
                "auto_color", autoColor, //
                "line_color", lineColor);

        String json = JSON.toJSONString(parameter);
        byte[] requestBody = StringUtil.getUtf8Bytes(json);

        Response response = HttpUtil.post(url, null, requestBody);
        log.info("#385 WeixinApi getWxaCodeUnlimit To:{} Response：{}", url, response.getStatus());

        return response.getResponseData();
    }

    /**
     * 获取微信Token，
     * 
     * @see #token(String, String)
     */
    @Override
    public String getWeixinToken(String appId, String appSecret) {
        WeixinTokenEntity token = this.token(appId, appSecret);
        return null == token ? null : token.getAccessToken();
    }

    /**
     * @see #getWeixinToken(String, String)
     */
    @Override
    public WeixinTokenEntity token(String appId, String appSecret) {
        Assert.hasText(appId, "#353 appId=" + appId);
        Assert.hasText(appSecret, "#354 appSecret=" + appSecret);

        String url = "https://api.weixin.qq.com/cgi-bin/token" //
                + "?grant_type=client_credential" //
                + "&appid=" + appId //
                + "&secret=" + appSecret;

        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.GET);
        Response response = request.execute();
        log.info("#345 token, response=" + (null == response ? response : response.toSimpleString()));

        String responseBody = response.getBody();
        Map<String, Object> responseMap = JSON.parseObject(responseBody);
        String token = StringUtil.toString(responseMap.get("access_token"));

        if (StringUtil.isEmpty(token)) {
            log.error("#362 token error, appId={}, appSecret={}, response={}", appId, appSecret, response);
            return null;
        }

        Integer expiresIn = NumberUtil.toInteger(responseMap.get("expires_in"));

        WeixinTokenEntity tokenEntity = new WeixinTokenEntity();
        tokenEntity.setAccessToken(token);
        tokenEntity.setExpiresIn(expiresIn);

        this.accessToken = token;

        return tokenEntity;
    }

    @Override
    public String get_industry() {
        Request request = new Request();
        request.setUrl("https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=" + this.getAccessToken());
        request.setMethod(Request.GET);
        Response response = request.execute();
        log.info("#368 token, response=" + (null == response ? response : response.toSimpleString()));

        String responseBody = response.getBody();
        return responseBody;
    }

    @Override
    public String api_set_industry() {
        throw new RuntimeException();
    }

    @Override
    public String userGet(String next_openid) {
        Request request = new Request();
        request.setUrl("https://api.weixin.qq.com/cgi-bin/user/get?access_token=" + this.getAccessToken());
        request.setMethod(Request.GET);
        Response response = request.execute();
        log.info("#385 userGet, response=" + (null == response ? response : response.toSimpleString()));

        String responseBody = response.getBody();
        return responseBody;
    }

    @Override
    public String wxCode2Session(String appKey, String appSecret, String code) {
        if (StringUtil.isNullOrEmpty(appKey) || StringUtil.isNullOrEmpty(appSecret) || StringUtil.isNullOrEmpty(code)) {
            throw new IllegalArgumentException("#467 wxCode2Session, appKey=" + appKey + ", appSecret=" + StringUtil.mask(appSecret) + ", code=" + code);
        }

        String url = "https://api.weixin.qq.com/sns/jscode2session" //
                + "?appid=" + appKey //
                + "&secret=" + appSecret //
                + "&js_code=" + code //
                + "&grant_type=authorization_code";

        Request request = new Request();
        request.setUrl(url);
        request.setMethod(Request.GET);
        Response response = request.execute();

        JSONObject jsonObject = JSON.parseObject(response.getBody());
        String openid = jsonObject.getString("openid");
        if (null != openid && !openid.trim().isEmpty()) {
            log.error("#464 jscode2session, response=" + (null == response ? response : response.toSimpleString()));
            return openid; // {"session_key":"HvQ0w1cO\/4z64q6gbI3nFA==","openid":"oSOA75JIYbHpHerUDUc4qncLakFs"}
        }

        log.error("#468 jscode2session, response=" + (null == response ? response : response.toSimpleString()));
        throw new RuntimeException("#465 jscode2session error"); // {"errcode":40029,"errmsg":"invalid code, hints: [ req_id: hEaDAa0gE-UMn3kA
                                                                 // ]"} 小程序 key 不对
    }
}